const Player = require("./Flow/Player");
const Room = require("./Flow/Room");
const Card = require("./Flow/Card");
const rule = require("./Flow/rule");
const Queue = require("./Flow/Queue");
const { User } = require("./router/admin");
const { setup: adminio_setup } = require("./adminio");

const {
  redis_client,
  redis_prefix,
  md5,
  db,
  on,
  successRes,
  errorRes,
  adminEvents
} = require("./unit");

async function wxGetUserInfo(code) {
  return {
    openid: md5("OPENID" + code),
    nickname: code,
    sex: "1",
    province: "PROVINCE",
    city: "CITY",
    country: "COUNTRY",
    headimgurl: "http://g-cdn.youdob.com/home/Avatar_01.png", //`http://api.adorable.io/avatars/50/${code}.png`,
    privilege: ["PRIVILEGE1", "PRIVILEGE2"],
    unionid: md5("UNIONID" + code),
    fake: true
  };
}

const ID_PLAYER_MAP = (exports.ID_PLAYER_MAP = new Map());
const ONLINE_PLAYER_SET = (exports.ONLINE_PLAYER_SET = new class PlayerSet
  extends Set {
  constructor(props) {
    super(props);
    this.player_number = 0;
  }
  set player_number(v) {
    if (v !== this._player_number) {
      this._player_number = v;
      adminEvents.emit("online-player-number-changed", v);
    }
  }
  get player_number() {
    return this._player_number;
  }
  add(player) {
    super.add(player);
    this.player_number = this.size;
  }
  delete(player) {
    super.delete(player);
    this.player_number = this.size;
  }
  clear() {
    super.clear();
    this.player_number = 0;
  }
}());
const SOCKET_PLAYER_WEAKMAP = (exports.SOCKET_PLAYER_WEAKMAP = new WeakMap());
const PLAYER_SOCKETS_WEAKMAP = (exports.PLAYER_SOCKETS_WEAKMAP = new WeakMap());
const SOCKET_ROOM_WEAKMAP = (exports.SOCKET_ROOM_WEAKMAP = new WeakMap());
const PLAYER_ROOM_WEAKMAP = Room.PLAYER_ROOM_WEAKMAP;

// 同步服务器上的用户数据
db.ref("users").on("child_changed", async function(sna) {
  // 监听服务器的数据改动，同步到本地服务器
  const new_user_info = sna.val();
  const user_kid = sna.key;
  console.log("[Firebase /users Changed]", user_kid, new_user_info.nickname);

  const user_session_info_json = await redis_client.getAsync(
    redis_prefix + "user-id-session-info-" + user_kid
  );
  if (user_session_info_json) {
    try {
      const user_session_info = JSON.parse(user_session_info_json);
      user_session_info.user_info = new User(new_user_info, user_kid);
      await redis_client.writeObjectAsync(
        redis_prefix + "user-id-session-info-" + user_kid,
        user_session_info
      );
    } catch (e) {
      console.log("user_session_info_json parse error", e);
      return;
    }
    // 更新内存中的用户信息
    if (ID_PLAYER_MAP.has(user_kid)) {
      const player = ID_PLAYER_MAP.get(user_kid);
      player.info = new_user_info;
      player.emitPropChanged("info");
    }
  }
});

const key_prefix_class_map = {
  room_: Room,
  user_: User,
  event_: {
    setting: {
      set bulletin(v) {
        this._bulletin = v;
        adminEvents.emit("bulletin-changed", v);
      },
      get bulletin() {
        return this._bulletin;
      }
    }
  }
};
// 同步服务器上的基础配置
db.ref("setting").on("value", async function(sna) {
  const new_setting = sna.val();
  if (!new_setting) {
    return;
  }
  const {
    room_max_round = 2,
    room_queue_max_round = 2,
    room_bot_duration = 5 * 60 * 1000,
    user_init_points = 0,
    user_share_resturn_points = 0
  } = new_setting;
  for (let key in new_setting) {
    let setting;
    let setting_key;
    let prefix;
    for (prefix in key_prefix_class_map) {
      if (key.startsWith(prefix)) {
        setting = key_prefix_class_map[prefix].setting;
        setting_key = key.substr(prefix.length);
        break;
      }
    }
    if (setting) {
      setting[setting_key] = new_setting[key];
      adminEvents.emit(
        prefix.replace("_", "-") + "setting-changed",
        setting,
        setting_key
      );
    }
  }
});

/**
 * 匹配模式
 */
const PLAYER_4_QUEUE_SET = (exports.PLAYER_4_QUEUE_SET = new Queue());
let go_in_to_room_number = 0;
PLAYER_4_QUEUE_SET.on("sizeChanged", size => {
  console.log("队列目前人数：", size);
  if (size - go_in_to_room_number >= 4) {
    const room_players = [];
    for (let player of PLAYER_4_QUEUE_SET) {
      if (player.status === Player.STATUS.IN_QUEUE) {
        room_players.push(player);
        if (room_players.length === 4) {
          break;
        }
      }
    }

    if (room_players.length === 4) {
      console.log("玩家人数满足，通知玩家准备开始游戏");

      const ready_players = new Set();
      let _time_out_cancel_group;
      let _is_group_runned = false;
      const cancel_group = reason => {
        if (_is_group_runned) {
          return;
        }
        _is_group_runned = true; // 只运行一次
        clearInterval(_time_out_cancel_group);
        console.log("组合取消，重新排队");
        if (ready_players.size === 4) {
          return;
        }
        go_in_to_room_number -= 4;
        if (reason === "time out") {
          // 由于超时导致的取消
          // 将所有未准备的玩家全部排除队列
          for (let player of room_players) {
            if (player.status === Player.STATUS.BEFORE_QUEUE_TO_ROOM) {
              // 将玩家重新进入队列排队
              player.reset(Player.STATUS.FREE);
            } else if (player.status === Player.STATUS.AFTER_QUEUE_TO_ROOM) {
              player.reset(Player.STATUS.IN_QUEUE);
            }
          }
        } else {
          // 有人手动取消队列
          // 将其余的所有玩家重新整合到原有的队列中，排序依旧靠前
          for (let player of room_players) {
            if (
              player.status === Player.STATUS.BEFORE_QUEUE_TO_ROOM ||
              player.status === Player.STATUS.AFTER_QUEUE_TO_ROOM
            ) {
              // 将玩家重新进入队列排队
              player.reset(Player.STATUS.IN_QUEUE);
            }
          }
        }
      };
      const confirm_group = () => {
        console.log("ready_players.size", ready_players.size);
        if (ready_players.size === 4) {
          //全部确认就绪，开始游戏
          if (_is_group_runned) {
            return;
          }
          _is_group_runned = true; // 只运行一次
          clearTimeout(_time_out_cancel_group);
          console.log("全部确认就绪，自动准备并开始游戏");
          go_in_to_room_number -= 4;
          const room = new Room(4, {
            mode: "自由匹配"
          });
          for (let player of room_players) {
            initRoom(player, room);
            room.addPlayer(player, false);
            player.reset(Player.STATUS.READY);
          }
        }
      };
      for (let player of room_players) {
        player.reset(Player.STATUS.BEFORE_QUEUE_TO_ROOM, [], true);
        setImmediate(() => {
          player.once("statusChanged", status => {
            if (status === Player.STATUS.AFTER_QUEUE_TO_ROOM) {
              ready_players.add(player);
              confirm_group();
            } else {
              cancel_group();
            }
          });
        });
      }

      let wait_time = 8;
      const count_time = () => {
        for (let player of room_players) {
          player.data = Object.assign(player.data || {}, {
            surplus_second: wait_time
          });
          player.emitPropChanged("data");
        }
        wait_time -= 1;
        if (wait_time < 0) {
          cancel_group("time out");
        }
      };
      _time_out_cancel_group = setInterval(count_time, 1000);
      go_in_to_room_number += 4;
    }
  }
});

function setup(koa_app, cb) {
  const server = require("http").createServer(koa_app.callback());
  const io = require("socket.io")(server);

  cb(io);

  io.on("connection", function(socket) {
    adminio_setup(socket);
    console.log("CONNECT!");
    // 公告功能
    const bulletin_binding_remover = on(
      adminEvents,
      "bulletin-changed",
      bulletin => {
        socket.emit("bulletin", bulletin);
      }
    );
    on(socket, "bulletin", () => {
      socket.emit(
        "bulletin",
        key_prefix_class_map.event_.setting.bulletin || ""
      );
    });
    // 设置功能
    const room_setting_binding_remover = on(
      adminEvents,
      "room-setting-changed",
      room_setting => {
        socket.emit("room-setting", room_setting);
      }
    );
    on(socket, "room-setting", () => {
      socket.emit("room-setting", Room.setting);
    });

    // console.log(Object.keys(socket),Object.keys(socket.conn.request))
    on(socket, "user-login", async data => {
      const { wx_code: user_session } = data;
      if (!user_session) {
        throw "请求参数错误";
      }
      const user_kid = await redis_client.getAsync(
        redis_prefix + "session-user-id-" + user_session
      );
      let login_user_info;
      if (user_kid) {
        const user_session_info = await redis_client.getAsync(
          redis_prefix + "user-id-session-info-" + user_kid
        );
        if (user_session_info) {
          try {
            const {
              user_session: effect_user_session,
              time_out,
              user_info
            } = JSON.parse(user_session_info);
            if (
              effect_user_session === user_session && time_out >= Date.now()
            ) {
              // 异步更新firebase中的最后登录时间
              if (user_info.openid) {
                console.log("用户登录成功，更新最后登录时间", user_info);
                login_user_info = user_info;
                db
                  .ref("users/" + user_kid + "/last_login_time")
                  .set(Date.now());
              } else {
                console.log("用户信息有误，强行请求重新登录");
                socket.emit("try-wx-login");
              }
            } else {
              if (time_out - Date.now() < 0) {
                console.log("用户登录超时，重新登录");
              } else {
                console.log("SESSION 有误，需要重新登录");
                console.log("db session", effect_user_session);
                console.log("user session", user_session);
              }
            }
          } catch (e) {}
        }
      }
      if (!login_user_info) {
        // 找不到用户，创建一个内存中的临时用户
        console.log("进行重新登录，发送微信登录跳转指令");
        login_user_info = await wxGetUserInfo(user_session);
        // 同时请求客户端重新登录
        socket.emit("try-wx-login");
      }
      initPlayer(login_user_info);
    });
    on(socket, "disconnect", function() {
      console.log("disconnect!");
      const player = SOCKET_PLAYER_WEAKMAP.get(socket);
      if (player) {
        SOCKET_PLAYER_WEAKMAP.delete(socket);
        const socket_set = PLAYER_SOCKETS_WEAKMAP.get(player);
        socket_set.delete(socket);
        player.is_online = !!socket_set.size;
      }
      SOCKET_ROOM_WEAKMAP.delete(socket);
      bulletin_binding_remover(); // 移除公告变动监听
      room_setting_binding_remover(); // 移除设置变动监听
    });

    function initPlayer(user_info) {
      const user_id = md5(user_info.openid);
      const player = ID_PLAYER_MAP.get(user_id) || new Player(user_info);
      player.singleOn(
        "is_onlineChanged",
        is_online => {
          if (is_online) {
            ONLINE_PLAYER_SET.add(player);
          } else {
            ONLINE_PLAYER_SET.delete(player);
          }
        },
        "WATCH-ONLINE-PLAYER-NUMBER"
      );
      player.is_online = true;
      ID_PLAYER_MAP.set(user_id, player);
      SOCKET_PLAYER_WEAKMAP.set(socket, player);
      if (PLAYER_SOCKETS_WEAKMAP.has(player)) {
        const socket_set = PLAYER_SOCKETS_WEAKMAP.get(player);
        if (socket_set.size) {
          console.log("用户在多端登录");
        }
        socket_set.add(socket);
      } else {
        const socket_set = new Set();
        socket_set.add(socket);
        PLAYER_SOCKETS_WEAKMAP.set(player, socket_set);
      }
      // 重新连接到房间
      const relink_room = PLAYER_ROOM_WEAKMAP.get(player);
      if (relink_room) {
        console.log("重新连接到房间", relink_room.id);
        initRoom(player, relink_room);
      }

      player.on("*Changed", changed_key => {
        // console.log("player *Changed", changed_key);
        socket.emit("player-frame", player);
      });
      socket.emit("user-login-success", player);
      // 发送初始数据
      socket.emit("player-frame", player);
      if (SOCKET_ROOM_WEAKMAP.has(socket)) {
        socket.emit("room-frame", SOCKET_ROOM_WEAKMAP.get(socket));
      }

      function checkIsInRoom() {
        const old_room = SOCKET_ROOM_WEAKMAP.get(socket);
        if (old_room) {
          throw `用户已经处于房间${old_room.id}中`;
        }
      }
      on(socket, "join-queue", data => {
        // 加入匹配模式队列
        player.joinQueue(PLAYER_4_QUEUE_SET);
      });
      on(socket, "confirm-queue-to-room", data => {
        // 确认队列到游戏
        player.confirmQueueToRoom();
      });

      on(socket, "cancel-queue", data => {
        // 确认队列到游戏
        player.cancelQueue();
      });

      on(socket, "create-room", data => {
        checkIsInRoom();
        const player_number = data.player_number | 0 || 2;
        // 检查是否有足够的房卡
        if (player.getCards() < 1) {
          throw new Error("房卡不足");
        }
        const new_room = new Room(player_number);
        initRoom(player, new_room);

        new_room.addPlayer(player);
      });
      on(socket, "join-room", data => {
        if (!data) {
          return;
        }

        const {
          room_id,
          room_hash_id,
          ignore_error // 使用宽容模式
        } = data;
        try {
          console.log("join-room-args:", data);
          checkIsInRoom();

          if (!room_id) {
            throw "请求参数错误";
          }
          const room = Room.getIns(room_id);
          if (!room) {
            throw `房间：${room_id}不存在`;
          }
          // 校验hash_id
          if (room_hash_id && room.hash_id !== room_hash_id) {
            return;
          }
          room.addPlayer(player);
          initRoom(player, room);
        } catch (e) {
          if (!ignore_error) {
            throw e;
          }
        }
      });

      on(socket, "get-user-game-logs", async data => {
        if (!data) {
          return;
        }
        const { from = 0, limit = 10, options = {} } = data;
        const res = await User.getGameLogs(player.id, from, limit, options);
        socket.emit("return-user-game-logs", res);
      });
    }
  });
  return server;
}

function initRoom(player, room) {
  const socket_set = PLAYER_SOCKETS_WEAKMAP.get(player);
  for (let socket of socket_set) {
    SOCKET_ROOM_WEAKMAP.set(socket, room);

    const BERS = [];

    BERS[BERS.length] = on(room, "*Changed", changed_key => {
      // console.log("room *Changed", changed_key);
      socket.emit("room-frame", room);
    });
    // 发送文本消息
    BERS[BERS.length] = on(socket, "send-msg", data => {
      if (!data) {
        return;
      }
      const { msg } = data;
      const formated_msg = msg.trim();
      room.players.forEach(other_player => {
        const socket_set = PLAYER_SOCKETS_WEAKMAP.get(other_player);
        for (let socket of socket_set) {
          socket.emit("player-send-msg", {
            player_id: player.id,
            player_name: player.info.nickname,
            msg: formated_msg
          });
        }
      });
    });

    BERS[BERS.length] = on(socket, "ready", data => {
      if (player.status === Player.STATUS.UN_READY) {
        player.reset(Player.STATUS.READY);
      }
    });
    BERS[BERS.length] = on(socket, "not-ready", data => {
      if (player.status === Player.STATUS.READY) {
        player.reset(Player.STATUS.UN_READY);
      }
    });
    BERS[BERS.length] = on(socket, "submit-res", data => {
      console.log("玩家提交结果");
      room.emit("player-submit-res", player, data);
    });
    const leave_room_handle = (reason, options = {}) => {
      const {
        reason_no_for_self,
        is_force_self_leave,
        is_force_other_leave,
        only_self_leave
      } = options;
      console.log("玩家离开房间", reason, options);
      if (room.removePlayer(player, is_force_self_leave)) {
        // 成功离开房间
        SOCKET_ROOM_WEAKMAP.delete(socket);
        // 开始销毁绑定的信息
        BERS.forEach(bind_event_remover => {
          bind_event_remover();
        });
        if (!only_self_leave) {
          if (
            room.owner === null || room.mode === "自由匹配" // 如果是房主离开房间了，那么其它人都要离开
          ) {
            // 自由匹配没有房主，但是如果有一人离开，那么所有人都需要自动离开
            room.players.slice().forEach(other_player => {
              other_player.emit("socket-emit-leave-room", reason, {
                is_force_self_leave: is_force_other_leave,
                only_self_leave: true
              });
            });
          }
        }
      }
      // 由于一个player有多个socke，所以removePlayer可能是false，但是消息要多出通知
      if (!reason_no_for_self) {
        if (typeof reason === "string" && reason) {
          console.log("通知");
          // 通知原因
          socket.emit("alert-notice", reason);
        }
      }
    };
    player.once("socket-emit-leave-room", function leave_room_from_player(
      reason = "房主解散房间",
      options
    ) {
      leave_room_handle(reason, options);
    });
    BERS[BERS.length] = on(socket, "leave-room", () => {
      if (room.mode === "自由匹配") {
        leave_room_handle("由于有玩家离开自由匹配房间, 自动解散, 请重新排队", {
          reason_no_for_self: true,
          is_force_other_leave: true
        });
      } else if (room.mode === "消费建房") {
        if (room.owner === player) {
          leave_room_handle("由于房主离开房间, 牌局自动解散", {
            reason_no_for_self: true,
            is_force_other_leave: true
          });
        } else {
          // 普通玩家的离开
          leave_room_handle();
        }
      }
    });

    // 获取统计信息
    BERS[BERS.length] = on(socket, "statistics-room", async data => {
      if (room.flow_statistics_promise) {
        socket.emit("room-statistics-res", await room.flow_statistics_promise);
      }
    });

    BERS[BERS.length] = on(socket, "recharge-room", async data => {
      if (room.owner === player) {
        await room.rechargeRoom();
      } else {
        await room.rechargeRoom(player);
        // console.log("当前玩家无权限执行此动作");
      }
    });

    BERS[BERS.length] = on(room, "end", res => {
      socket.emit("end-game", res);
    });
    BERS[BERS.length] = on(socket, "restart", data => {
      room.restartFlow(player);
    });
    // 作弊器
    BERS[BERS.length] = on(socket, "--changed-card-list--", data => {
      player.removeCards(player.card_list.slice());
      player.addCards(data.map(card_txt => Card.getIns(card_txt)));
    });
    // 检测是否特殊牌
    BERS[BERS.length] = on(socket, "is-special-card-list", data => {
      const res_info = {};
      if (Array.isArray(data)) {
        const card_list = data
          .map(card_txt => Card.getIns(card_txt))
          .filter(card => card);
        rule.is_special(card_list, res_info);
      }
      socket.emit("special-card-list-res", res_info);
    });
  }
}

function destroyRoom(room) {}
exports.setup = setup;
